"""
Objective-C 符号提取器

支持两种提取模式：
1. AST模式：使用 clang 编译器进行精确的语法树解析（推荐）
2. 正则模式：使用正则表达式进行快速但不太精确的提取（回退方案）
"""

import re
import json
import subprocess
from typing import Dict, List, Optional, Tuple
from pathlib import Path

from core.extractors_module.base_extractor import LanguageExtractor
from core.interfaces_module.extractor import SymbolInfo


class ObjCExtractor(LanguageExtractor):
    """
    Objective-C 符号提取器

    支持:
    - 类 (@interface, @implementation)
    - 分类 (Category)
    - 扩展 (Extension)
    - 方法 (实例方法/类方法)
    - 属性 (@property)
    - 协议 (@protocol)
    - 实例变量
    - 类型别名 (typedef)
    - 枚举 (enum)
    - 结构体 (struct)
    """

    def get_language_name(self) -> str:
        """获取语言名称"""
        return "objc"

    def get_compiler_command(self) -> str:
        """获取编译器命令"""
        return "clang"

    def get_file_extensions(self) -> List[str]:
        """获取文件扩展名"""
        return ['.h', '.m', '.mm']

    def _should_use_ast(self) -> bool:
        """
        判断是否应该使用 AST 提取

        Returns:
            如果可以且应该使用 AST 返回 True
        """
        # 检查是否有 clang 编译器
        if not self.is_available():
            if self.config and self.config.analysis.require_ast:
                raise RuntimeError(
                    "需要 AST 提取但 clang 编译器未找到。"
                    "请安装 Xcode Command Line Tools 或 LLVM。"
                )
            return False

        # 如果配置要求 AST 或没有明确禁用，则使用 AST
        if self.config:
            if self.config.analysis.require_ast:
                return True
            if not self.config.analysis.allow_fallback:
                return True

        return self.is_available()  # 如果有编译器就用

    def extract_symbols_from_file(self, file_path: str) -> Dict:
        """
        从单个 Objective-C 文件提取符号

        优先使用 AST 提取，失败时降级到正则提取

        Args:
            file_path: 文件路径

        Returns:
            符号字典
        """
        # 尝试使用 AST 提取
        if self._should_use_ast():
            try:
                return self._extract_objc_ast(file_path)
            except Exception as e:
                if self.config and self.config.analysis.log_fallback:
                    self.logger.log_warning(
                        f"ObjC AST 提取失败，降级到正则模式: {file_path}\n原因: {e}"
                    )
                if self.config and not self.config.analysis.allow_fallback:
                    raise

        # 降级到正则提取
        return self._extract_objc_regex(file_path)

    def _extract_objc_ast(self, file_path: str) -> Dict:
        """
        使用 clang AST 提取 Objective-C 符号

        Args:
            file_path: ObjC 文件路径

        Returns:
            符号字典
        """
        symbols = {
            'classes': {},
            'categories': {},
            'protocols': {},
            'methods': {},
            'properties': {},
            'ivars': {},
            'typedefs': {},
            'enums': {},
            'structs': {},
            'file_symbols': {file_path: []}
        }

        try:
            # 构建 clang 命令来输出 AST
            # 使用 -Xclang -ast-dump 输出 AST
            # -fsyntax-only 只做语法分析，不生成代码
            cmd = [
                self.get_compiler_command(),
                '-Xclang', '-ast-dump',
                '-fsyntax-only',
                '-ObjC',  # 指定 Objective-C 模式
                '-fobjc-arc',  # 启用 ARC
                file_path
            ]

            # 如果配置中有额外的头文件搜索路径，添加它们
            if self.config and self.config.analysis.extra_header_search_paths:
                for path in self.config.analysis.extra_header_search_paths:
                    cmd.extend(['-I', path])

            # 执行命令
            timeout = self.config.analysis.ast_timeout if self.config else 30
            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
                timeout=timeout
            )

            if result.returncode != 0 and not result.stdout:
                # 如果失败但没有输出，尝试使用更宽松的选项
                cmd = [
                    self.get_compiler_command(),
                    '-Xclang', '-ast-dump',
                    '-fsyntax-only',
                    '-x', 'objective-c',  # 明确指定语言
                    '-w',  # 忽略警告
                    file_path
                ]
                result = subprocess.run(
                    cmd,
                    capture_output=True,
                    text=True,
                    timeout=timeout
                )

            # 解析 AST 输出
            ast_output = result.stdout if result.stdout else result.stderr
            if ast_output:
                self._parse_clang_ast(ast_output, file_path, symbols)
            else:
                raise Exception("没有 AST 输出")

        except subprocess.TimeoutExpired:
            raise Exception(f"AST 提取超时 ({timeout}秒)")
        except Exception as e:
            raise Exception(f"AST 提取失败: {e}")

        return symbols

    def _parse_clang_ast(self, ast_output: str, file_path: str, symbols: Dict):
        """
        解析 clang AST 输出

        Clang AST 输出格式示例：
        |-ObjCInterfaceDecl 0x7f8b2c0a8e00 <line:5:1, line:10:2> line:5:12 MyClass
        | |-ObjCMethodDecl 0x7f8b2c0a8f80 <line:7:1, col:22> col:1 - doSomething 'void'
        | `-ObjCPropertyDecl 0x7f8b2c0a9020 <line:8:1, col:30> col:30 myProperty 'NSString *'

        Args:
            ast_output: AST 输出字符串
            file_path: 源文件路径
            symbols: 符号字典
        """
        lines = ast_output.split('\n')
        current_class = None
        current_category = None
        current_protocol = None

        for line in lines:
            # 跳过不在当前文件的声明
            if file_path not in line and '<invalid sloc>' in line:
                continue

            # 提取行号信息
            line_number = self._extract_line_number(line)

            # 类声明
            if 'ObjCInterfaceDecl' in line:
                match = re.search(r'ObjCInterfaceDecl.*?(\w+)$', line)
                if match:
                    class_name = match.group(1)
                    current_class = class_name
                    symbols['classes'][class_name] = SymbolInfo(
                        name=class_name,
                        type='class',
                        file=file_path,
                        line=line_number,
                        column=0,
                        modifiers=['interface']
                    )
                    symbols['file_symbols'][file_path].append(class_name)

            # 实现声明
            elif 'ObjCImplementationDecl' in line:
                match = re.search(r'ObjCImplementationDecl.*?(\w+)$', line)
                if match:
                    class_name = match.group(1)
                    current_class = class_name
                    if class_name not in symbols['classes']:
                        symbols['classes'][class_name] = SymbolInfo(
                            name=class_name,
                            type='class',
                            file=file_path,
                            line=line_number,
                            column=0,
                            modifiers=['implementation']
                        )

            # 分类声明
            elif 'ObjCCategoryDecl' in line:
                match = re.search(r'ObjCCategoryDecl.*?(\w+)\s+(\w+)', line)
                if match:
                    class_name = match.group(1)
                    category_name = match.group(2)
                    current_category = f"{class_name}+{category_name}"
                    symbols['categories'][current_category] = SymbolInfo(
                        name=category_name,
                        type='category',
                        file=file_path,
                        line=line_number,
                        column=0,
                        modifiers=['category', f'class:{class_name}']
                    )
                    symbols['file_symbols'][file_path].append(current_category)

            # 协议声明
            elif 'ObjCProtocolDecl' in line:
                match = re.search(r'ObjCProtocolDecl.*?(\w+)$', line)
                if match:
                    protocol_name = match.group(1)
                    current_protocol = protocol_name
                    symbols['protocols'][protocol_name] = SymbolInfo(
                        name=protocol_name,
                        type='protocol',
                        file=file_path,
                        line=line_number,
                        column=0,
                        modifiers=['protocol']
                    )
                    symbols['file_symbols'][file_path].append(protocol_name)

            # 方法声明
            elif 'ObjCMethodDecl' in line:
                # 匹配格式: ObjCMethodDecl ... - methodName 'returnType'
                # 或: ObjCMethodDecl ... + classMethod 'returnType'
                match = re.search(r'ObjCMethodDecl.*?([-+])\s+(\w+)', line)
                if match:
                    method_type = match.group(1)
                    method_name = match.group(2)

                    # 提取返回类型
                    return_type_match = re.search(r"'([^']*)'", line)
                    return_type = return_type_match.group(1) if return_type_match else 'id'

                    modifiers = ['class_method' if method_type == '+' else 'instance_method']
                    if current_class:
                        modifiers.append(f'class:{current_class}')
                    elif current_protocol:
                        modifiers.append(f'protocol:{current_protocol}')

                    # 生成唯一的方法键（包含类/协议信息）
                    method_key = f"{current_class or current_protocol}_{method_name}" if (current_class or current_protocol) else method_name

                    symbols['methods'][method_key] = SymbolInfo(
                        name=method_name,
                        type='method',
                        file=file_path,
                        line=line_number,
                        column=0,
                        modifiers=modifiers,
                        return_type=return_type
                    )

            # 属性声明
            elif 'ObjCPropertyDecl' in line:
                match = re.search(r'ObjCPropertyDecl.*?(\w+)\s+\'([^\']+)\'', line)
                if match:
                    prop_name = match.group(1)
                    prop_type = match.group(2)

                    modifiers = ['property']
                    if current_class:
                        modifiers.append(f'class:{current_class}')

                    # 提取属性修饰符 (readonly, nonatomic, strong 等)
                    if 'readonly' in line:
                        modifiers.append('readonly')
                    if 'nonatomic' in line:
                        modifiers.append('nonatomic')
                    if 'strong' in line:
                        modifiers.append('strong')
                    elif 'weak' in line:
                        modifiers.append('weak')
                    elif 'copy' in line:
                        modifiers.append('copy')

                    symbols['properties'][prop_name] = SymbolInfo(
                        name=prop_name,
                        type='property',
                        file=file_path,
                        line=line_number,
                        column=0,
                        modifiers=modifiers,
                        return_type=prop_type
                    )

            # 实例变量
            elif 'ObjCIvarDecl' in line:
                match = re.search(r'ObjCIvarDecl.*?(\w+)\s+\'([^\']+)\'', line)
                if match:
                    ivar_name = match.group(1)
                    ivar_type = match.group(2)

                    modifiers = ['ivar']
                    if current_class:
                        modifiers.append(f'class:{current_class}')

                    symbols['ivars'][ivar_name] = SymbolInfo(
                        name=ivar_name,
                        type='ivar',
                        file=file_path,
                        line=line_number,
                        column=0,
                        modifiers=modifiers,
                        return_type=ivar_type
                    )

            # typedef 声明
            elif 'TypedefDecl' in line:
                match = re.search(r'TypedefDecl.*?(\w+)\s+\'([^\']+)\'', line)
                if match:
                    typedef_name = match.group(1)
                    typedef_type = match.group(2)

                    symbols['typedefs'][typedef_name] = SymbolInfo(
                        name=typedef_name,
                        type='typedef',
                        file=file_path,
                        line=line_number,
                        column=0,
                        return_type=typedef_type
                    )
                    symbols['file_symbols'][file_path].append(typedef_name)

            # 枚举声明
            elif 'EnumDecl' in line:
                match = re.search(r'EnumDecl.*?(\w+)', line)
                if match:
                    enum_name = match.group(1)
                    symbols['enums'][enum_name] = SymbolInfo(
                        name=enum_name,
                        type='enum',
                        file=file_path,
                        line=line_number,
                        column=0
                    )
                    symbols['file_symbols'][file_path].append(enum_name)

            # 结构体声明
            elif 'RecordDecl' in line and 'struct' in line:
                match = re.search(r'RecordDecl.*?struct\s+(\w+)', line)
                if match:
                    struct_name = match.group(1)
                    symbols['structs'][struct_name] = SymbolInfo(
                        name=struct_name,
                        type='struct',
                        file=file_path,
                        line=line_number,
                        column=0
                    )
                    symbols['file_symbols'][file_path].append(struct_name)

    def _extract_line_number(self, ast_line: str) -> int:
        """
        从 AST 行中提取行号

        Args:
            ast_line: AST 输出的一行

        Returns:
            行号，如果无法提取返回 0
        """
        # 尝试匹配 line:数字 格式
        match = re.search(r'line:(\d+)', ast_line)
        if match:
            return int(match.group(1))

        # 尝试匹配 <行:列> 格式
        match = re.search(r'<.*?:(\d+):', ast_line)
        if match:
            return int(match.group(1))

        return 0

    def _extract_objc_regex(self, file_path: str) -> Dict:
        """
        使用正则表达式提取 Objective-C 符号（回退方案）

        Args:
            file_path: 文件路径

        Returns:
            符号字典
        """
        symbols = {
            'classes': {},
            'categories': {},
            'protocols': {},
            'methods': {},
            'properties': {},
            'ivars': {},
            'typedefs': {},
            'enums': {},
            'structs': {},
            'file_symbols': {file_path: []}
        }

        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                content = f.read()

            # 移除注释以避免误匹配
            content = self._remove_comments(content)

            # 提取接口声明
            interface_pattern = r'@interface\s+(\w+)\s*(?::\s*(\w+))?'
            for match in re.finditer(interface_pattern, content):
                class_name = match.group(1)
                line_number = content[:match.start()].count('\n') + 1

                symbols['classes'][class_name] = SymbolInfo(
                    name=class_name,
                    type='class',
                    file=file_path,
                    line=line_number,
                    column=0,
                    modifiers=['interface']
                )
                symbols['file_symbols'][file_path].append(class_name)

            # 提取实现声明
            implementation_pattern = r'@implementation\s+(\w+)'
            for match in re.finditer(implementation_pattern, content):
                class_name = match.group(1)
                if class_name not in symbols['classes']:
                    line_number = content[:match.start()].count('\n') + 1
                    symbols['classes'][class_name] = SymbolInfo(
                        name=class_name,
                        type='class',
                        file=file_path,
                        line=line_number,
                        column=0,
                        modifiers=['implementation']
                    )

            # 提取分类声明
            category_pattern = r'@interface\s+(\w+)\s*\((\w+)\)'
            for match in re.finditer(category_pattern, content):
                class_name = match.group(1)
                category_name = match.group(2)
                line_number = content[:match.start()].count('\n') + 1

                category_key = f"{class_name}+{category_name}"
                symbols['categories'][category_key] = SymbolInfo(
                    name=category_name,
                    type='category',
                    file=file_path,
                    line=line_number,
                    column=0,
                    modifiers=['category', f'class:{class_name}']
                )
                symbols['file_symbols'][file_path].append(category_key)

            # 提取方法声明 (- 实例方法, + 类方法)
            # 改进的正则，支持多参数方法
            method_pattern = r'^([-+])\s*\(([^)]*)\)\s*(\w+)(?::|\s|;)'
            for match in re.finditer(method_pattern, content, re.MULTILINE):
                method_type = match.group(1)
                return_type = match.group(2).strip() or 'id'
                method_name = match.group(3)
                line_number = content[:match.start()].count('\n') + 1

                modifiers = ['class_method' if method_type == '+' else 'instance_method']

                symbols['methods'][method_name] = SymbolInfo(
                    name=method_name,
                    type='method',
                    file=file_path,
                    line=line_number,
                    column=0,
                    modifiers=modifiers,
                    return_type=return_type
                )

            # 提取属性声明（改进的正则）
            property_pattern = r'@property\s*(?:\([^)]*\))?\s*([\w\s\*<>]+)\s+(\w+)'
            for match in re.finditer(property_pattern, content):
                prop_type = match.group(1).strip()
                prop_name = match.group(2)
                line_number = content[:match.start()].count('\n') + 1

                modifiers = ['property']
                # 提取属性修饰符
                property_line = content[match.start():match.end()]
                if 'readonly' in property_line:
                    modifiers.append('readonly')
                if 'nonatomic' in property_line:
                    modifiers.append('nonatomic')

                symbols['properties'][prop_name] = SymbolInfo(
                    name=prop_name,
                    type='property',
                    file=file_path,
                    line=line_number,
                    column=0,
                    modifiers=modifiers,
                    return_type=prop_type
                )

            # 提取协议声明
            protocol_pattern = r'@protocol\s+(\w+)'
            for match in re.finditer(protocol_pattern, content):
                protocol_name = match.group(1)
                line_number = content[:match.start()].count('\n') + 1

                symbols['protocols'][protocol_name] = SymbolInfo(
                    name=protocol_name,
                    type='protocol',
                    file=file_path,
                    line=line_number,
                    column=0,
                    modifiers=['protocol']
                )
                symbols['file_symbols'][file_path].append(protocol_name)

            # 提取 typedef
            typedef_pattern = r'typedef\s+(?:struct\s+)?(?:enum\s+)?[\w\s\*]+\s+(\w+)\s*;'
            for match in re.finditer(typedef_pattern, content):
                typedef_name = match.group(1)
                line_number = content[:match.start()].count('\n') + 1

                symbols['typedefs'][typedef_name] = SymbolInfo(
                    name=typedef_name,
                    type='typedef',
                    file=file_path,
                    line=line_number,
                    column=0
                )
                symbols['file_symbols'][file_path].append(typedef_name)

            # 提取枚举
            enum_pattern = r'(?:typedef\s+)?enum\s+(\w+)'
            for match in re.finditer(enum_pattern, content):
                enum_name = match.group(1)
                line_number = content[:match.start()].count('\n') + 1

                symbols['enums'][enum_name] = SymbolInfo(
                    name=enum_name,
                    type='enum',
                    file=file_path,
                    line=line_number,
                    column=0
                )
                symbols['file_symbols'][file_path].append(enum_name)

            # 提取结构体
            struct_pattern = r'(?:typedef\s+)?struct\s+(\w+)'
            for match in re.finditer(struct_pattern, content):
                struct_name = match.group(1)
                line_number = content[:match.start()].count('\n') + 1

                symbols['structs'][struct_name] = SymbolInfo(
                    name=struct_name,
                    type='struct',
                    file=file_path,
                    line=line_number,
                    column=0
                )
                symbols['file_symbols'][file_path].append(struct_name)

        except Exception as e:
            self.logger.log_error(f"解析 ObjC 文件失败 {file_path}: {e}")

        return symbols

    def _remove_comments(self, content: str) -> str:
        """
        移除 Objective-C 代码中的注释

        Args:
            content: ObjC 源代码

        Returns:
            移除注释后的代码
        """
        # 移除单行注释
        content = re.sub(r'//.*?$', '', content, flags=re.MULTILINE)
        # 移除多行注释
        content = re.sub(r'/\*.*?\*/', '', content, flags=re.DOTALL)
        return content

    def extract_symbols_batch(self, files: List[str]) -> Dict:
        """
        批量提取 Objective-C 符号

        优先使用 AST 模式，必要时降级到正则模式

        Args:
            files: ObjC 文件列表

        Returns:
            合并后的符号字典
        """
        if not files:
            return {}

        mode = "AST" if self._should_use_ast() else "正则"
        self.logger.log_operation(f"ObjC 分析 ({mode}模式)", f"处理 {len(files)} 个文件")

        symbols = {
            'classes': {},
            'categories': {},
            'protocols': {},
            'methods': {},
            'properties': {},
            'ivars': {},
            'typedefs': {},
            'enums': {},
            'structs': {},
            'file_symbols': {}
        }

        success_count = 0
        failed_files = []

        for file_path in files:
            try:
                file_symbols = self.extract_symbols_from_file(file_path)
                self._merge_symbols(symbols, file_symbols)
                success_count += 1
            except Exception as e:
                failed_files.append(file_path)
                self.logger.log_error(f"处理文件失败 {file_path}: {e}")

        if failed_files and self.config and self.config.analysis.log_fallback:
            self.logger.log_warning(
                f"ObjC 提取完成：成功 {success_count}/{len(files)} 个文件\n"
                f"失败文件: {', '.join(failed_files[:5])}..."
            )

        return symbols

    def _merge_symbols(self, target: Dict, source: Dict):
        """
        合并符号字典

        Args:
            target: 目标字典
            source: 源字典
        """
        for key in source:
            if key == 'file_symbols':
                target[key].update(source[key])
            elif key in target:
                target[key].update(source[key])
            else:
                target[key] = source[key]